﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//    ChannelControlTest.cs
//
// Abstract:
//    
//    Unit tests for Enhanced Reminders channels selection UI.
//
// Classes:
//
//    Microsoft.EnhancedReminders.OutlookAddIn.UnitTests.ChannelControlTest
//
//--

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Microsoft.EnhancedReminders.OutlookAddIn.UnitTests
{       
    /// <summary>
    ///This is a test class for ChannelControlTest and is intended
    ///to contain all ChannelControlTest Unit Tests
    ///</summary>
    [TestClass()]
    public class ChannelControlTest
    {
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        /// <summary>
        ///A test for initializing ChannelControl.
        ///</summary>
        [TestMethod()]
        public void InitializeTest()
        {
            ObservableCollection<NotificationChannel> channels = CreateDefaultChannels();
            ChannelControl_Accessor accessor = CreateChannelAccessor(channels);

            Assert.AreEqual(channels.Count, accessor.Channels.Count, "accessor.Channels");

            VerifyState(accessor, /*addEnabled*/true, /*editEnabled*/false, /*deleteEnabled*/false, /*upEnabled*/false, /*downEnabled*/false, /*allCheckboxChecked*/true);
        }

        /// <summary>
        /// Test that changes to the channels list (after initializing) trigger a 
        /// property changed event to notify that the channels should be saved.
        ///</summary>
        [TestMethod()]
        public void TestControlUpdatesWhenChannelsListChanges()
        {
            ChannelControl_Accessor accessor = new ChannelControl_Accessor();

            bool channelsUpdated = false;

            ((ChannelControl)accessor.Target).PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "Channels")
                {
                    channelsUpdated = true;
                }
            };

            // Initialize the control.
            accessor.Initialize(CreateDefaultChannels());
            Assert.IsFalse(channelsUpdated, "Initializing channels should not trigger an update notification");

            // Add a channel
            accessor.Channels.Add(new NotificationChannelUI(new NotificationChannel(), accessor.Target as ChannelControl));
            Assert.IsTrue(channelsUpdated, "Expected notification when a channel is added");

            // Modify a channel channels 
            channelsUpdated = false;
            accessor.Channels[0].IsEnabled = !accessor.Channels[0].IsEnabled;
            Assert.IsTrue(channelsUpdated, "Expected notification when a channel is changed");

            // Remove a channel
            channelsUpdated = false;
            accessor.Channels.RemoveAt(0);
            Assert.IsTrue(channelsUpdated, "Expected notification when a channel is removed");
        }

        /// <summary>
        /// Test adding a channel.
        ///</summary>
        [TestMethod()]
        public void TestAddingAChannel()
        {
            ChannelControl_Accessor accessor = CreateChannelAccessor(null);

            NotificationChannel addedChannel = new NotificationChannel();
            addedChannel.UserDefinedName = "Channel added to top";

            int channelCount = accessor.Channels.Count;

            // Add a channel to the top of the list.
            accessor.InnerAddEditChannel(/*editingChannel*/false, accessor.Channels.Count, addedChannel, /*addToTop*/true);
         
            Assert.AreEqual(channelCount + 1, accessor.Channels.Count, "Expecting channelCount + 1");

            Assert.IsTrue(addedChannel.Equals(accessor.Channels[0].Channel), "Expecting channel added to top");

            // Test adding a channel to the bottom of the list.
            NotificationChannel addedChannel2 = addedChannel.Copy();
            addedChannel2.UserDefinedName = "Channel added to bottom";

            accessor.InnerAddEditChannel(/*editingChannel*/false, accessor.Channels.Count, addedChannel2, /*addToTop*/false);

            Assert.AreEqual(channelCount + 2, accessor.Channels.Count, "Expecting channelCount + 1");

            Assert.IsTrue(addedChannel2.Equals(accessor.Channels[accessor.Channels.Count - 1].Channel), "Channel should be added to bottom");
        }

        /// <summary>
        /// Test editing a channel.
        ///</summary>
        [TestMethod()]
        public void TestEditingAChannel()
        {
            ChannelControl_Accessor accessor = CreateChannelAccessor(null);

            const int selectedChannel = 1;

            // Take a copy of the channel to edit (the second channel)
            NotificationChannel originalChannel = accessor.Channels[selectedChannel].Channel.Copy();

            // Test editing a channel in place.
            NotificationChannel updatedChannel = originalChannel.Copy();
            updatedChannel.UserDefinedName = "Channel has been edited";
            updatedChannel.IsEnabled = !originalChannel.IsEnabled;
            accessor.InnerAddEditChannel(/*editingChannel*/true, selectedChannel, updatedChannel, /*addToTop*/false);

            Assert.IsTrue(updatedChannel.Equals(accessor.Channels[selectedChannel].Channel), "Channel should be updated");

            // Test editing a channel and moving to the top of the list.
            updatedChannel.UserDefinedName = "Channel has been updated again and moved to the top";
            accessor.InnerAddEditChannel(/*editingChannel*/false, selectedChannel, updatedChannel, /*addToTop*/true);

            Assert.IsTrue(updatedChannel.Equals(accessor.Channels[0].Channel), "Channel should be updated and moved to the top");
        }

        /// <summary>
        /// Test deleting a channel.
        ///</summary>
        [TestMethod()]
        public void TestDeletingAChannel()
        {
            ObservableCollection<NotificationChannel> channels = CreateDefaultChannels();
            ChannelControl_Accessor accessor = CreateChannelAccessor(channels);

            // Delete the 1st channel
            accessor.channelsListBox.SelectedIndex = 0;
            accessor.DeleteChannel(/*warnUserBeforeDeleting*/false);

            Assert.AreEqual(channels.Count - 1, accessor.Channels.Count, "A channel should be deleted");

            // Verify that the first item is what was originally the second.
            Assert.IsTrue(channels[1].Equals(accessor.Channels[0].Channel), "The first item should have been removed");
        }

        /// <summary>
        /// Test moving a channel up.
        ///</summary>
        [TestMethod()]
        public void TestMovingAChannelUp()
        {
            ObservableCollection<NotificationChannel> channels = CreateDefaultChannels();
            ChannelControl_Accessor accessor = CreateChannelAccessor(channels);

            Assert.AreEqual(2, channels.Count, "Expecting two channels");

            // Verify move up button is not enabled.
            Assert.IsFalse(ChannelControl_Accessor.UpCommand.CanExecute(this, accessor.upButton), "Up button should not be enabled");

            // Select the last item.
            accessor.channelsListBox.SelectedIndex = channels.Count - 1;

            Assert.IsTrue(ChannelControl_Accessor.UpCommand.CanExecute(this, accessor.upButton), "Up button should be enabled");

            // Move the last item to the top
            ChannelControl_Accessor.UpCommand.Execute(this, accessor.upButton);

            Assert.IsFalse(ChannelControl_Accessor.UpCommand.CanExecute(this, accessor.upButton), "Up button should not be enabled after move");

            // Channels should now be swapped
            Assert.AreEqual<NotificationChannel>(accessor.Channels[0].Channel, channels[1], "channel[0]");
            Assert.AreEqual<NotificationChannel>(accessor.Channels[1].Channel, channels[0], "channel[1]");
        }

        /// <summary>
        /// Test moving a channel down.
        ///</summary>
        [TestMethod()]
        public void TestMovingAChannelDown()
        {
            ObservableCollection<NotificationChannel> channels = CreateDefaultChannels();
            ChannelControl_Accessor accessor = CreateChannelAccessor(channels);

            Assert.AreEqual(2, channels.Count, "Expecting two channels to be created");

            // Verify down button is not enabled.
            Assert.IsFalse(ChannelControl_Accessor.DownCommand.CanExecute(this, accessor.downButton), "down button is not initially enabled");

            // Select the first item
            accessor.channelsListBox.SelectedIndex = 0;

            Assert.IsTrue(ChannelControl_Accessor.DownCommand.CanExecute(this, accessor.downButton), "down button is enabled before move");

            // Move the first item to the bottom
            ChannelControl_Accessor.DownCommand.Execute(this, accessor.downButton);

            Assert.IsFalse(ChannelControl_Accessor.DownCommand.CanExecute(this, accessor.downButton), "down button is not enabled after move");

            // Channels should now be swapped
            Assert.AreEqual<NotificationChannel>(accessor.Channels[0].Channel, channels[1], "channels[1]");
            Assert.AreEqual<NotificationChannel>(accessor.Channels[1].Channel, channels[0], "channels[0]");
        }

        /// <summary>
        /// Test the "enable all" checkbox.  Note that this checkbox is bound to the AllEnabled property.  However
        /// the binding does not seem to work when the control is not displayed and so we need to simulate 
        /// the binding by forcibly setting AllEnabled and the checkbox "IsChecked" property.  This then verifies
        /// that the correct notifications are sent to WPF to update the "enable all" checkbox.
        ///</summary>
        [TestMethod()]
        public void TestAllEnabledCheckbox()
        {
            ChannelControl_Accessor accessor = CreateChannelAccessor(null);

            // Hook up to PropertyChanged to receive notifications that the AllEnabled property was updated.
            bool wasNotified = false;
            accessor.add_PropertyChanged(delegate(object sender, PropertyChangedEventArgs e)
            {
                if (e != null && e.PropertyName == "AllEnabled")
                {
                    wasNotified = true;
                }
            });

            Assert.IsTrue(accessor.AllEnabled, "All checkboxes initially checked");

            // Verify unchecking all channels         
            accessor.AllEnabled = false;
            foreach (NotificationChannelUI channel in accessor.Channels)
            {
                Assert.IsFalse(channel.IsEnabled, "Uncheck all");
            }

            // Verify checking all channels
            accessor.AllEnabled = true;
            foreach (NotificationChannelUI channel in accessor.Channels)
            {
                Assert.IsTrue(channel.IsEnabled, "Check all");
            }

            // Verify unchecking a channel also unchecks the All checkbox
            wasNotified = false;
            accessor.enableAll.IsChecked = true;
            accessor.Channels[0].IsEnabled = false;
            Assert.IsTrue(wasNotified && !accessor.AllEnabled, "Unchecking a channel");

            // Verify checking the previous channel also checks the All checkbox
            wasNotified = false;
            accessor.enableAll.IsChecked = false;
            accessor.Channels[0].IsEnabled = true;
            Assert.IsTrue(wasNotified && accessor.AllEnabled, "Re-checking a channel");

            // Verify adding a channel which is not enabled disables the checkbox
            wasNotified = false;
            accessor.enableAll.IsChecked = true;
            NotificationChannel addChannel = new NotificationCustomChannel();
            addChannel.IsEnabled = false;
            Assert.IsFalse(addChannel.IsEnabled, "Channel should be enabled");
            accessor.InnerAddEditChannel(false, accessor.Channels.Count, addChannel, /*addToTop*/true);
            Assert.IsTrue(wasNotified && !accessor.AllEnabled, "Adding a non-enabled channel");

            // Verify removing the only unchecked channel re-checks the checkbox
            wasNotified = false;
            accessor.enableAll.IsChecked = false;
            accessor.channelsListBox.SelectedIndex = 0;
            accessor.DeleteChannel(false);
            Assert.IsTrue(wasNotified && accessor.AllEnabled, "Deleting the only unchecked channel");
        }

        /// <summary>
        /// Helper method to create a ChannelControl private accessor.
        /// </summary>
        private ChannelControl_Accessor CreateChannelAccessor(ObservableCollection<NotificationChannel> channels)
        {
            if (channels == null)
            {
                channels = CreateDefaultChannels();
            }

            ChannelControl_Accessor accessor = new ChannelControl_Accessor();

            accessor.Initialize(channels);

            return accessor;
        }
        
        /// <summary>
        /// Helper method to verify the state of the control.
        /// </summary>
        private void VerifyState(ChannelControl_Accessor accessor, 
                                 bool addEnabled, 
                                 bool editEnabled,
                                 bool deleteEnabled,
                                 bool upEnabled,
                                 bool downEnabled,
                                 bool allCheckboxChecked)
        {
            Assert.AreEqual(addEnabled, ChannelControl_Accessor.AddCommand.CanExecute(this, accessor.addButton), "addEnabled");

            Assert.AreEqual(editEnabled, ChannelControl_Accessor.EditCommand.CanExecute(this, accessor.editButton), "editEnabled");

            Assert.AreEqual(deleteEnabled, ChannelControl_Accessor.DeleteCommand.CanExecute(this, accessor.deleteButton), "deleteEnabled");

            Assert.AreEqual(upEnabled, ChannelControl_Accessor.UpCommand.CanExecute(this, accessor.upButton), "upEnabled");

            Assert.AreEqual(downEnabled, ChannelControl_Accessor.DownCommand.CanExecute(this, accessor.downButton), "downEnabled");

            Assert.AreEqual(allCheckboxChecked, accessor.AllEnabled, "allCheckboxChecked");
        }

        /// <summary>
        /// Helper method to simulate creation of the default channels.
        /// </summary>
        /// <returns></returns>
        private ObservableCollection<NotificationChannel> CreateDefaultChannels()
        {
            ObservableCollection<NotificationChannel> channels = new ObservableCollection<NotificationChannel>();

            NotificationChannel channel = new NotificationChannel(NotificationChannelType.Communicator, "Communicator", "x.y");
            channel.IsEnabled = true;
            channels.Add(channel);

            channel = new NotificationChannel(NotificationChannelType.Phone, "Phone", "1234567");
            channel.IsEnabled = true;
            channels.Add(channel);

            return channels;
        }
    }
}
